package orc;

import model.MyRect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.awt.image.ImageFormatException;
import util.MathUtils;
import util.RegexUtil;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;

/**
 * Created by wrh on 17-10-2.
 */
public class Img {

    private static Logger logger = LoggerFactory.getLogger(Img.class);


    private BufferedImage bufferedImage;
    private int height;
    private int weight;
    //核的大小
    private int kernelSize;

    public Img(BufferedImage bufferedImage){
        this.bufferedImage=bufferedImage;
        this.weight = bufferedImage.getWidth();
        this.height = bufferedImage.getHeight();
        logger.info("图像宽高分别为:"+weight+"-"+height);
        this.kernelSize=weight/9;
    }

    public Img(File img) throws IOException {
        this(ImageIO.read(img));
    }

    public Img(String imgPath) throws IOException {
        this(new File(imgPath));
    }

    /**
     * 二值化灰度图像
     * 大于或小于阀值的像素全部为白色,其他为黑色
     * @param threshold 阀值(0-255)
     * @param rect 二值化的区域,为null时全图进行二值化
     * @return
     */
    public Img gray2Binary(int threshold,MyRect rect) {

        int startx=0;
        int starty=0;
        int endx=weight;
        int endy=height;

        if (rect!=null){
            startx = rect.getLeft();
            starty = rect.getTop();
            endx = rect.getRight();
            endy = rect.getBottom();
        }
        logger.info("二值化开始,阀值"+threshold+"区域"+"{bottom="+endy+", left="+startx+", right="+endx+", top="+starty+"}");
        //依次循环，对图像的像素进行处理
        for (int i=startx; i<endx; i++) {
            for (int j=starty; j < endy; j++) {
                //得到当前像素的值
                int col = bufferedImage.getRGB(i,j);
                //得到alpha通道的值
                int alpha = col & 0xFF000000;
                //计算灰度值
                int gray = calGray(col);
                  //对图像进行二值化处理
                if (gray <= threshold) {
                    gray = 0;
                } else {
                    gray = 255;
                }
                // 新的ARGB
                int newColor = alpha | (gray << 16) | (gray << 8) | gray;
                bufferedImage.setRGB(i,j,newColor);
            }
        }
        if (rect!=null){
            logger.info(rect.toString()+"二值化结束");
        }
        return this;
    }

    /**
     * 计算灰度值
     * @param col RGB值
     * @return 灰度值
     */
    public static int calGray(int col) {
        //得到图像的像素RGB的值
        int red = (col & 0x00FF0000) >> 16;
        int green = (col & 0x0000FF00) >> 8;
        int blue = (col & 0x000000FF);
        // 用公式X = 0.3×R+0.59×G+0.11×B计算出X代替原来的RGB
        int gray = (int) ((float) red * 0.3 + (float) green * 0.59 + (float) blue * 0.11);

        return gray;
    }

    /**
     * 以内核形式二值化图像
     * 适用于光线不均匀情况下图像的处理
     * @param kernelSize 核的尺寸
     * @return
     */
    public Img gray2BinaryByKernel(int kernelSize){
        this.kernelSize  = kernelSize;
        gray2BinaryByKernel();
        return this;
    }

    /**
     * 同上,直接使用时核大小为默认值 orc.Img#kernelSize
     * @return
     */
    public Img gray2BinaryByKernel(){

        //横纵方向核的数量
        int kernelNumInX=weight/kernelSize;
        int kernelNumInY=height/kernelSize;

        for (int x=0;x<kernelNumInX;x++){
            for(int y=0;y<kernelNumInY;y++){
                //内核灰度值数组
                int[][] kernelGray= new int[kernelSize][kernelSize];
                for(int i=0;i<kernelSize;i++){
                    for (int j=0;j<kernelSize;j++){
                        int pixX = x*kernelSize+i;
                        int pixY=y*kernelSize+j;
                        if (pixX<weight && pixY<height){
                            kernelGray[i][j] = calGray(bufferedImage.getRGB(pixX,pixY));
                        }
                    }
                }
                //转换直方数组
                int[] kernelGrayArr=countGray(kernelGray);
                logger.info("直方数组"+MathUtils.intArr2Str(kernelGrayArr));

                //大梯度平滑灰度值
                kernelGrayArr = smooth(kernelGrayArr,30);
                logger.info("大梯度平滑"+MathUtils.intArr2Str(kernelGrayArr));
                //去除独峰
                kernelGrayArr = removeIndependentPeak(kernelGrayArr,kernelSize*kernelSize/255+kernelSize);
                logger.info("去除孤峰"+MathUtils.intArr2Str(kernelGrayArr));
                //二次平滑
                kernelGrayArr = smooth(kernelGrayArr,10);
                logger.info("二次平滑"+kernelGrayArr);
//                获取双峰低谷值
//                int threshold=getThresholdDoublePeak(kernelGrayArr);

                /*********************************/
                //一阶微分,
                int[] firstOrder=calFirstOrder(kernelGrayArr);

                //取微分最大的色阶
                int threshold=0;
                int max=firstOrder[0];
                for (int i=1;i<firstOrder.length;i++){
                    if (firstOrder[i]>max){
                        max = firstOrder[i];
                        threshold=i;
                    }
                }
                //阀值矫正
                if (threshold>75){
                    threshold=threshold-20;
                }
                if (threshold<50 || threshold>200){
                    threshold=145;
                }


                /*********************************/
                //对值越界进行处理
                int startx=x*kernelSize;
                int starty=y*kernelSize;
                int endx = startx+kernelSize;
                int endy = starty+kernelSize;
                logger.info("内核边界+"+startx+"-"+starty+"-"+endx+"-"+endy);
                if (weight-endx<kernelSize){
                    endx = weight;
                }
                if (height-endy<kernelSize){
                    endy = height;
                }
                MyRect rect = new MyRect(startx,starty,endx,endy);

                //二值化当前核
                gray2Binary(threshold, rect);

            }
        }


        return this;
    }

    /**
     * 去除孤峰
     * @param kernelGrayArr 直方图数组
     * @param maxDifferent 最大差值(即定义孤峰与相邻柱的数量差,大于为孤峰,否则不是)
     * @return
     */
    public static int[] removeIndependentPeak(int[] kernelGrayArr,int maxDifferent) {
        logger.info("去除孤峰开始,maxDifferent="+maxDifferent);
        int[] result=kernelGrayArr;
        for (int i=1;i<kernelGrayArr.length-1;i++){
            if (result[i]>result[i-1] && result[i]>result[i+1]){
                if (result[i]-result[i-1]>maxDifferent && result[i]-result[i+1]>maxDifferent){
                    result[i] = (result[i-1]+result[i+1])/2;
                }
            }
        }
        logger.info("去除孤峰结束");
        return result;
    }


    /**
     * 计算灰度数值双峰谷底值
     * @param kernelGray 灰度数组
     * @return 谷底值
     */
    private int getThresholdDoublePeak(int[] kernelGray) {

        logger.info("计算灰度数值双峰谷底值开始",MathUtils.intArr2Str(kernelGray));
        int[] numOfGray=kernelGray;

        //判断是否为双峰图
        //TODO
        int threshold = doubleHumpTrough(numOfGray);
        if(threshold!=NO_TROUGH){
            return threshold;
        }
        return 95;
    }

    //没有谷底值(不是双峰直方图)
    public static final int NO_TROUGH=-1;
    /**
     * 计算双峰直方图谷值
     * @param numOfGray
     * @return 如果为双峰图,返回低谷值,否则返回NO_TROUGH
     */
    public static int doubleHumpTrough(int[] numOfGray) {
        //一阶微分
        int[] firstOrderDifferential = calFirstOrder(numOfGray);
        logger.info("一阶微分后"+MathUtils.intArr2Str(firstOrderDifferential));
        //单一化 simplification()
        int[] simArr  = simplification(firstOrderDifferential);
        logger.info("单一化后"+MathUtils.intArr2Str(simArr));
        //判断是否符合双峰模型
        int trough=isDoublePeak(simArr);
        if(trough>=0){
            return trough;
        }
        return NO_TROUGH;
    }


    public static final String NEGATIVE_NUMBER= "n";//负数
    public static final String POSITIVE_NUMBER= "p";//正数
    public static final String ZERO="z";//零
    //上升模型
    public static final String RISE_MODEL="("+POSITIVE_NUMBER+"+"+ZERO+"*"+POSITIVE_NUMBER+"*"+")+";
    //下降模型
    public static final String DECLINE_MODEL="("+NEGATIVE_NUMBER+"+"+ZERO+"*"+NEGATIVE_NUMBER+"*"+")+";
    //双峰模型(严格说,这是谷模型,针对此项目,可做为双峰模型处理)
    //0* +* 0* --* 0* ++* 0* -* 0*
    public static final String DONBLE_PEAK_MODEL=DECLINE_MODEL+RISE_MODEL;

    /**
     * 判断是否符合双峰模型双峰
     * @param simArr 一阶微分且单一化后的数组
     * @return 若果是,返回谷值,如果不是返回-1
     */
    private static int isDoublePeak(int[] simArr) {
        String simString = "";
        int result=-1;
        //转义便于正则判断
        for (int i=0;i<simArr.length;i++){
            if (simArr[i]>0) simString+=POSITIVE_NUMBER;
            else if(simArr[i]<0) simString+=NEGATIVE_NUMBER;
            else simString+=ZERO;
        }
        logger.info("单一化后转义simString="+simString);
        //去重
        String shortSimString = RegexUtil.removeSame(simString);
        logger.info("去重后simString:"+simString);
        Map double_peak=RegexUtil.inContentStart(shortSimString,DONBLE_PEAK_MODEL);

        if (double_peak!=null){
            double_peak=RegexUtil.inContentStart(simString,DONBLE_PEAK_MODEL);
            int start=Integer.parseInt(String.valueOf(double_peak.get("start")));
            int end =Integer.parseInt(String.valueOf(double_peak.get("end")));

            logger.info("符合双峰模型起始和结束位置"+start+" "+end);
            Map trough = RegexUtil.inContentStart(simString.substring(start,end),RISE_MODEL);
            if (trough!=null){
                result = Integer.parseInt(String.valueOf(trough.get("start")));
                logger.info("符合上升模型起始位置"+result);
                return result;
            }
        }

        return result;
    }

    /**
     * 单一化数组,(正数标记1,负数标记-1)
     * @param arr
     * @return
     */
    public static int[] simplification(int[] arr) {
        int[] result=new int[arr.length];
        for (int i=0;i<arr.length;i++){
            if (arr[i]>0) result[i]=1;
            else if(arr[i]<0) result[i]=-1;
            else result[i]=0;
        }
        return result;
    }

    /**
     * 计算一阶导数
     * @param numOfGray 数组
     * @return 各整数值点对应的导数
     */
    public static int[] calFirstOrder(int[] numOfGray) {
        int[] result=new int[numOfGray.length];
        for (int i=1;i<numOfGray.length-1;i++){
            result[i]=(numOfGray[i+1]-numOfGray[i-1])/2;
        }
        return result;
    }

    /**
     * 灰度值计数
     * @param kernelGray
     * @return
     */
    public int[] countGray(int[][] kernelGray){
        int numOfGray[] = new int[256];
        for (int i=0;i<kernelSize;i++){
            for (int j=0;j<kernelSize;j++){
                int gray = kernelGray[i][j];
                if (gray<0) gray=0;
                if (gray>255) gray=255;
                numOfGray[gray]++;
            }
        }
        return numOfGray;
    }


    /**
     * 平滑直方图
     * @param arr 直方图数组
     * @param step 步长(0-255/4),默认为30
     * @return 平滑后直方图数组
     */
    public static int[] smooth(int[] arr, int step) {
        if (step<0 || step>255){
            step=30;
        }
        int start = step - step/2;
        int end = 256 - step/2 ;
        double temp;

        int[] res = new int[256];

        for (int i = start; i < end; i++) {
            temp = 0;
            for (int j = 0-step/2; j < step/2; j++) {
                temp += arr[i + j];
            }
            temp /= step;
            res[i] = (int)temp;
        }
        return res;
    }

    /**
     * 将图片保存到文件中
     * @param imgPath 图片路径
     * @throws IOException
     * @return
     */
    public boolean saveTofile(String imgPath) throws IOException {

        //获取文件后缀名
        String formatName=imgPath.substring(imgPath.lastIndexOf(".") + 1);
        if (!isImageFormat(formatName)){
            formatName = "jpg";
        }
        File img = new File(imgPath);
        boolean write = ImageIO.write(bufferedImage,formatName , img);
        return write;
    }

    /**
     * 判断是否是图片格式名称
     * @param formatName 格式名称
     * @return
     */
    public static boolean isImageFormat(String formatName) {
        String upperCase = formatName.toUpperCase();
        for (ImageFormat format:ImageFormat.values()) {
            if (upperCase.equals(format.name()))
                return true;
        }
        return false;
    }

    public enum ImageFormat{
        BMP,JPG,PNG,TIFF,GIF,PCX,TGA,EXIF,FPX,SVG,PSD,CDR,PCD,DXF,UFO,EPS,AI,RAW,WMF
    }

    public BufferedImage getBufferedImage() {
        return this.bufferedImage;
    }
}
